/*
 * OREKIT-X
 * Copyright 2002-2008 CS Communication & Systemes
 * 
 * Parts of this software package have been licensed to CS
 * Communication & Systemes (CS) under one or more contributor license
 * agreements.  See the NOTICE file distributed with this work for
 * additional information.
 *  
 * This is an experimental copy of OREKIT from www.orekit.org.
 * Please use the original OREKIT from orekit.org for normal work
 * unrelated to this research project.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you
 * may not use this file except in compliance with the License.  You
 * may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package ore.attitudes;

import ore.Attitude;
import ore.CelestialBody;
import ore.Frame;
import ore.PositionVelocity;
import ore.Transform;
import ore.errors.OrekitException;
import ore.time.AbsoluteDate;

import org.apache.commons.math.geometry.Rotation;
import org.apache.commons.math.geometry.Vector3D;

/**
 * The celestial body pointed law is defined by two main elements:
 * <ul>
 *   <li>a celestial body towards which some satellite axis is exactly aimed</li>
 *   <li>a phasing reference defining the rotation around the pointing axis</li>
 * </ul>
 * 
 * The celestial body implicitly defines two of the three degrees of
 * freedom and the phasing reference defines the last degree of
 * freedom. This definition can be represented as first aligning
 * exactly the satellite pointing axis to the current direction of the
 * celestial body, and then to find the rotation around this axis such
 * that the satellite phasing axis is in the half-plane defined by a
 * cut line on the pointing axis and containing the celestial phasing
 * reference.
 * 
 * In order for this definition to work, the user must ensure that the
 * phasing references are <strong>never</strong> aligned with the
 * pointing references.  Since the pointed body moves as the date
 * changes, this should be ensured regardless of the date. A simple
 * way to do this for Sun, Moon or any planet pointing is to choose a
 * phasing reference far from the ecliptic plane. Using
 * <code>Vector3D.PLUS_K</code>, the equatorial pole, is perfect in
 * these cases.
 * 
 * Instances of this class are immutable.
 * 
 * @author Luc Maisonobe
 */
public class CelestialBodyPointed 
    extends Object
    implements ore.AttitudeLaw
{
    /**
     * Frame in which {@link #pointedBody} and {@link #phasingCel} are defined.
     */
    public final Frame celestialFrame;
    /**
     * Celestial body to point at.
     */
    public final CelestialBody pointedBody;
    /**
     * Phasing reference, in celestial frame.
     */
    public final Vector3D phasingCel;
    /**
     * Satellite axis aiming at the pointed body, in satellite frame.
     */
    public final Vector3D pointingSat;
    /**
     * Phasing reference, in satellite frame.
     */
    public final Vector3D phasingSat;


    /** 
     * @param celestialFrame frame in which <code>pointedBody</code>
     * and <code>phasingCel</code> are defined
     * @param pointedBody celestial body to point at
     * @param phasingCel phasing reference, in celestial frame
     * @param pointingSat satellite vector defining the pointing direction
     * @param phasingSat phasing reference, in satellite frame
     */
    public CelestialBodyPointed(Frame celestialFrame, CelestialBody pointedBody,
                                Vector3D phasingCel, Vector3D pointingSat, Vector3D phasingSat)
    {
        super();
        this.celestialFrame = celestialFrame;
        this.pointedBody    = pointedBody;
        this.phasingCel     = phasingCel;
        this.pointingSat    = pointingSat;
        this.phasingSat     = phasingSat;
    }


    /** {@inheritDoc} */
    public Attitude getState(AbsoluteDate date,
                             PositionVelocity pv, Frame frame)
        throws OrekitException
    {
        /*
         * Compute celestial references at the specified date
         */
        PositionVelocity bodyPV    = pointedBody.getPositionVelocity(date, celestialFrame);
        PositionVelocity satCel    = frame.getTransformTo(celestialFrame, date).transformPositionVelocity(pv);
        PositionVelocity pointing  = new PositionVelocity(1.0, bodyPV, -1.0, satCel);
        Vector3D       pointingPos = pointing.getPosition();
        double r2 = Vector3D.dotProduct(pointingPos, pointingPos);
        /*
         * Evaluate instant rotation axis
         */
        Vector3D rotAxisCel =
            new Vector3D( (1.0 / r2), 
                          Vector3D.crossProduct(pointingPos, pointing.getVelocity()));
        /*
         * Compute transform from celestial frame to satellite frame
         */
        Rotation celToSatRotation =
            new Rotation(pointingPos, phasingCel, pointingSat, phasingSat);

        Vector3D celToSatSpin = celToSatRotation.applyTo(rotAxisCel);

        Transform transform = new Transform(celToSatRotation, celToSatSpin);

        if (frame != celestialFrame) {
            /*
             * Prepend transform from specified frame to celestial frame
             */
            transform = new Transform(frame.getTransformTo(celestialFrame, date), transform);
        }

        return new Attitude(frame, transform.getRotation(), transform.getRotationRate());
    }

}
